package com.wltt.healing.healinguserservice.config.exception;

import com.wltt.healing.healinguserservice.model.ResultData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

/**
 * @Author: wltt
 * @Date: 2019/10/22 14:04
 * @Version: 1.0
 * @Description: TODO
 */
@Component
@Order(-100)
public class GlobalErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    private static final Logger logger = LogManager.getLogger(GlobalErrorWebExceptionHandler.class);

    public GlobalErrorWebExceptionHandler(ApplicationContext applicationContext,
                                          ErrorAttributes errorAttributes,
                                          WebProperties.Resources resources,
                                          ServerProperties serverProperties,
                                          ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, resources, serverProperties.getError(), applicationContext);
        super.setMessageWriters(serverCodecConfigurer.getWriters());
        super.setMessageReaders(serverCodecConfigurer.getReaders());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    @Override
    protected Mono<ServerResponse> renderErrorResponse(final ServerRequest request) {
        Throwable throwable = getError(request);
        logger.debug(throwable);
        if (throwable instanceof BusinessException) {
            ResultData resultData = ResultData.failure(throwable.getMessage());
            return ServerResponse.status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(resultData));
        } else {
            MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations.from(throwable.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
            String errorMessage = determineMessage(throwable, responseStatusAnnotation);
            logger.error(errorMessage);
            HttpStatus errorStatus = determineHttpStatus(throwable, responseStatusAnnotation);
            ResultData resultData = ResultData.failure("系统异常");
            return ServerResponse.status(errorStatus.value())
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(resultData));
        }
    }

    private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        return error instanceof ResponseStatusException ? ((ResponseStatusException) error).getStatus() : (HttpStatus) responseStatusAnnotation.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        if (error instanceof BindingResult) {
            return error.getMessage();
        } else if (error instanceof ResponseStatusException) {
            return ((ResponseStatusException) error).getReason();
        } else {
            String reason = (String) responseStatusAnnotation.getValue("reason", String.class).orElse("");
            if (StringUtils.hasText(reason)) {
                return reason;
            } else {
                return error.getMessage() != null ? error.getMessage() : "";
            }
        }
    }

}
